<?php

class Kd_Aop_Compiler {

    static private $_cutinMethods = array();
    static private $_ignoreMethods = array();

    static public function compile(){
        $compiledPath = Kd_Config::getTmpPath().'aop'.DIRECTORY_SEPARATOR;
        if(is_dir($compiledPath)){
            return;
        }
        mkdir($compiledPath, '0777', TRUE);

        self::$_ignoreMethods = Kd_Config::getSetting('aop.ignoreMethods', array());
        
        foreach(Kd_Aop_Manager::getPointcutins() as $pointcutin){
            self::$_cutinMethods = $pointcutin['methods'];
            $className = $pointcutin['target'];
//            $classNames = explode("_", $className);
//            $classFile = array_pop($classNames);
            $subPath = str_replace(' ', DIRECTORY_SEPARATOR, ucwords(str_replace('_', ' ', $className)));
            foreach(Kd_Package::getIncludePaths() as $includePath){
                if(file_exists($includePath.$subPath.'.php')){
                    $classContent = file_get_contents($includePath.$subPath.'.php');
                    break;
                }
            }

            if(empty($classContent)){
                throw new Kd_Aop_Exception("Not found class [$className].");
            }
            
            preg_match_all('/public\s+function\s+.*\{/', $classContent, $methods);
            $classContent = preg_replace_callback('/function\s+(\w+)\s*/', array('Kd_Aop_Compiler', 'filterMethod'), $classContent);

            $aopContent = '';
            foreach($methods[0] as $method){
                preg_match('/function\s+([a-zA-Z0-9_]+)\(/', $method, $nameMatches);
                
                if(substr($nameMatches[1], 0, 2) == '__' || 
                   in_array($nameMatches[1], self::$_ignoreMethods) ||
                   !in_array($nameMatches[1], self::$_cutinMethods)){
                    continue;
                }

                preg_match('/\(.*\)/', $method, $argumentMatches);
                $aopContent .= "$method\n";
                $aopContent .= "\t$"."args = func_get_args();\n";
                $aopContent .= "\tKd_Aop_Observer::notify(Kd_Aop_Observer::$"."INVOKE_BEFORE, $"."this, '$nameMatches[1]', $"."arg);\n";
                $aopContent .= "\t$"."result = call_user_func_array(array($"."this, 'aop_{$nameMatches[1]}'), $"."args);\n";
                $aopContent .= "\tKd_Aop_Observer::notify(Kd_Aop_Observer::$"."INVOKE_AFTER, $"."this, '$nameMatches[1]', array($"."result));\n";
                $aopContent .= "\treturn $"."result;\n";
                $aopContent .= "}\n";
            }

            $pos = strrpos($classContent, '}');
            $classContent = substr($classContent, 0, $pos)."\n\n$aopContent\n".substr($classContent, $pos);
            $proxyFile = $compiledPath.$subPath.'.php';
            mkdir(dirname($proxyFile), '0777', TRUE);
            file_put_contents($proxyFile, $classContent);
        }
    }

    static private function filterMethod($matches){
        if(substr($matches[1], 0, 2) == '__'){
            return $matches[0];
        }

        if(in_array($matches[1], self::$_ignoreMethods)){
            return $matches[0];
        }

        if(!in_array($matches[1], self::$_cutinMethods)){
            return $matches[0];
        }

        return 'function aop_'.$matches[1];
    }
}